/*
 * $Id: TemplateUtils.java 37 2012-12-28 09:15:03Z vladimirow@mail.ru $
 * $HeadURL: http://ultralog.googlecode.com/svn/ultralog/tags/release-20130123/src/java/com/google/code/ultralog/template/TemplateUtils.java $
 *
 * This file is a part of ultralog project (http://code.google.com/p/ultralog/).
 * Released under New BSD License.
 */
package com.google.code.ultralog.template;

import java.util.ArrayList;
import java.util.List;

import com.google.code.ultralog.template.Template.Placeholder;

/**
 * Contains various useful methods to work with templates.
 *
 * @see Template
 *
 * @author Mikhail Vladimirov
 */
public class TemplateUtils
{
    private TemplateUtils ()
    {
        throw new Error ("Do not instantiate me");
    }

    /**
     * Parse template.  Template is a string with zero or more placeholder.
     * Each placeholder has the following structure:
     *
     * <pre>'$' '{' &lt;name&gt; [ ':' &lt;options&gt; ] '}'</pre>
     *
     * Here &lt;name&gt; is a string of character that should not contain colon
     * (':') or right brace ('}') characters.  Options is a string of characters
     * that may contain arbitrary characters, though  special characters such
     * as right brace ('}') or backslash ('\') should be escaped with backslash.
     *
     * If dollar sign ('$') ought to be placed outside placeholders, it should
     * be doubled.  Here is an example of valid template:
     *
     * <pre>Current price of ${fruit} at the market is $$${price:p2}.</pre>
     *
     * @param template template
     * @return parsed template
     * @throws IllegalArgumentException if template is invalid
     */
    public static Template parseTemplate (String template)
        throws IllegalArgumentException
    {
        if (template == null)
            throw new IllegalArgumentException ("Template is null");

        List <String> literals = new ArrayList <String> ();
        List <String> placeholderNames = new ArrayList <String> ();
        List <String> placeholderOptions = new ArrayList <String> ();

        int state = 0;
        int position = 0;
        int length = template.length ();
        StringBuilder token = new StringBuilder ();

        while (position < length)
        {
            char ch = template.charAt (position);

            switch (state)
            {
            case 0: // Initial state
                switch (ch)
                {
                case '$':
                    state = 1;
                    break;
                default:
                    token.append (ch);
                    break;
                }
                break;
            case 1: // After first dollar
                switch (ch)
                {
                case '$':
                    token.append (ch);
                    state = 0;
                    break;
                case '{':
                    literals.add (token.toString ());
                    token.setLength (0);
                    state = 2;
                    break;
                default:
                    throw new IllegalArgumentException (
                        "Unexpected character `" + ch +"' at position " +
                        position + " in template: " + template);
                }
                break;
            case 2: // Name
                switch (ch)
                {
                case '}':
                    placeholderNames.add (token.toString ());
                    placeholderOptions.add ("");
                    token.setLength (0);
                    state = 0;
                    break;
                case ':':
                    placeholderNames.add (token.toString ());
                    token.setLength (0);
                    state = 3;
                    break;
                default:
                    token.append (ch);
                    break;
                }
                break;
            case 3: // Options
                switch (ch)
                {
                case '}':
                    placeholderOptions.add (token.toString ());
                    token.setLength (0);
                    state = 0;
                    break;
                case '\\':
                    state = 4;
                    break;
                default:
                    token.append (ch);
                    break;
                }
                break;
            case 4: // After first backslash
                token.append (ch);
                state = 3;
                break;
            default:
                throw new Error ("Unknown state: " + state);
            }

            position += 1;
        }

        switch (state)
        {
        case 0:
            literals.add (token.toString ());
            break;
        default:
            throw new IllegalArgumentException (
                "Unexpected end of template: " + template);
        }

        int placeholdersCount = placeholderNames.size ();
        if (placeholderOptions.size () != placeholdersCount)
            throw new Error (
                "Number of placeholder names and options is not the same");

        Placeholder [] placeholders = new Placeholder [placeholdersCount];
        for (int i = 0; i < placeholdersCount; i++)
            placeholders [i] =
                new MyPlaceholder (
                    placeholderNames.get (i), placeholderOptions.get (i));

        return new MyTemplate (
            literals.toArray (new String [literals.size ()]), placeholders);
    }

    private static class MyTemplate implements Template
    {
        private final String [] literals;
        private final Placeholder [] placeholders;

        public MyTemplate (String [] literals, Placeholder [] placeholders)
        {
            if (literals == null)
                throw new IllegalArgumentException ("Literals is null");

            if (placeholders == null)
                throw new IllegalArgumentException ("Placeholders is null");

            int literalsLength = literals.length;
            int placeholdersLength = placeholders.length;

            if (literalsLength != placeholdersLength + 1)
                throw new IllegalArgumentException (
                    "Literals length (" + literalsLength +
                    ") is not equals to placeholders length (" +
                    placeholdersLength + ") + 1");

            this.literals = literals.clone ();
            this.placeholders = placeholders.clone ();
        }

        @Override
        public int getPlaceholderCount ()
        {
            return placeholders.length;
        }

        @Override
        public String getLiteralAt (int position)
        {
            if (position < 0)
                throw new IllegalArgumentException (
                    "Position (" + position + ") < 0");

            int literalsCount = literals.length;

            if (position >= literalsCount)
                throw new IllegalArgumentException (
                    "Position (" + position + ") >= literals count (" +
                    literalsCount + ")");

            return literals [position];
        }

        @Override
        public Placeholder getPlaceholderAt (int position)
        {
            if (position < 0)
                throw new IllegalArgumentException (
                    "Position (" + position + ") < 0");

            int placeholdersCount = placeholders.length;

            if (position >= placeholdersCount)
                throw new IllegalArgumentException (
                    "Position (" + position + ") >= placeholders count (" +
                            placeholdersCount + ")");

            return placeholders [position];
        }
    }

    private static class MyPlaceholder implements Placeholder
    {
        private final String name;
        private final String options;

        public MyPlaceholder (String name, String options)
        {
            if (name == null)
                throw new IllegalArgumentException ("Name is null");

            if (options == null)
                throw new IllegalArgumentException ("Options is null");

            this.name = name;
            this.options = options;
        }

        @Override
        public String getName ()
        {
            return name;
        }

        @Override
        public String getOptions ()
        {
            return options;
        }
    }
}
